﻿using UnityEngine;
using UnityEngine.UIElements;
using winS.Unity;
using Math = winS.Unity.Math;

namespace winS.UnityEditor.UIElement
{
    public class SliderInt : EditorField<int>
    {
        public static readonly int defaultMinValue = 0;
        public static readonly int defaultMaxValue = 10;
        public static readonly float valueFieldWidth = 40f;

        public int minValue { get => _minValue; set => SetMinValue(value); }
        public int maxValue { get => _maxValue; set => SetMaxValue(value); }

        private int _minValue = defaultMinValue;
        private int _maxValue = defaultMaxValue;
        private int undoPreValue;

        private readonly IntegerField valueField;
        private readonly SliderIntElement sliderIntElement;

        internal SliderInt(ElementFactory elementFactory) : base(elementFactory)
        {
            Add(sliderIntElement = new SliderIntElement(this, 1f / (maxValue - minValue)));

            Add(valueField = new IntegerField());
            valueField.RegisterValueChangedCallback(OnValueChangedCallback);
            valueField.style.SetFixedWidth(valueFieldWidth);
            valueField.style.SetMargin(0f);
        }

        public void SetRange(int minValue, int maxValue)
        {
            _minValue = minValue;
            _maxValue = maxValue;
            sliderIntElement.SetInternal(1f / (maxValue - minValue));
            UpdateSilderPosition();
        }
        public override void SetValueWithoutNotify(int value)
        {
            base.SetValueWithoutNotify(value);
            UpdateSilderPosition();
            UpdateDisplayValue();
        }

        protected override void SetValue(int value)
        {
            SetValueWithoutNotify(value);
            OnValueChanged(value);
            UpdateSilderPosition();
            UpdateDisplayValue();
        }

        private void SetMinValue(int newMinValue)
        {
            _minValue = newMinValue;
            if (value < _minValue) value = _minValue;
            UpdateSilderPosition();
        }
        private void SetMaxValue(int newMaxValue)
        {
            _maxValue = newMaxValue;
            if (value > _maxValue) value = _maxValue;
            UpdateSilderPosition();
        }

        private void UpdateDisplayValue()
        {
            valueField.SetValueWithoutNotify(value);
        }
        private void UpdateSilderPosition()
        {
            sliderIntElement.SetPosition(Math.Clamp((float)(value - minValue) / (maxValue - minValue), 0f, 1f));
        }

        private void OnSliderStopCallback()
        {
            SendUndoValueChangeEvent(undoPreValue, value);
        }
        private void OnSliderStartCallback()
        {
            undoPreValue = value;
        }
        private void OnValueChangedCallback(ChangeEvent<int> changeEvent)
        {
            int newValue = changeEvent.newValue;
            bool clamp = Math.Clamp(ref newValue, minValue, maxValue);
            base.SetValue(newValue);
            UpdateSilderPosition();
            if (clamp) UpdateDisplayValue();
        }
        private void OnSliderChangedCallback(float ratio)
        {
            int value = minValue + (int)(ratio * (maxValue - minValue));
            SetValueWithoutNotify(value);
            OnValueChanged(value);
            UpdateDisplayValue();
        }

        private class SliderIntElement : VisualElement
        {
            public static readonly float radius = 6f;
            public static readonly Color defaultBarColor = ColorLevelUtility.GetColor(9);
            public static readonly Color defaultHandleColor = ColorLevelUtility.GetColor(14);

            private readonly SliderInt sliderInt;
            private readonly EditorElement handle;

            private bool dragging;

            private ArithmeticProgression arithmeticProgression;

            public SliderIntElement(SliderInt sliderInt, float interval)
            {
                this.sliderInt = sliderInt;
                SetInternal(interval);

                style.flexGrow = 1f;
                style.justifyContent = Justify.Center;
                style.paddingLeft = radius;
                style.marginRight = sliderInt.elementFactory.marginHorizontal * 2f + radius;

                VisualElement bar = EditorElementUtility.elementFactory.CreateEditorElementWithName("滑轨", defaultBarColor);
                Add(bar);
                bar.style.SetBorderRadius(1f);
                bar.style.SetHeight(2f);

                Add(handle = EditorElementUtility.elementFactory.CreateEditorElementWithName("滑柄", defaultHandleColor));
                handle.style.SetSize(radius * 2f);
                handle.style.SetBorderRadius(radius);
                handle.style.SetBorderWdith(1f);
                handle.style.position = Position.Absolute;

                EditorElementUtility.ApplyFocusElementStyle(handle, Color.clear);
                RegisterCallback<MouseUpEvent>(MouseUpCallback);
                RegisterCallback<MouseDownEvent>(MouseDownCallback);
                RegisterCallback<MouseMoveEvent>(MouseMoveCallback);
            }

            public void SetInternal(float interval)
            {
                arithmeticProgression = new ArithmeticProgression(0f, interval);
            }
            public void SetPosition(float ratio)
            {
                handle.style.SetLeft(ratio * 100f, LengthUnit.Percent);
            }

            private void MouseUpCallback(MouseUpEvent mouseUpEvent)
            {
                dragging = false;
                this.ReleaseMouse();
                handle.style.SetBorderColor(Color.clear);
                handle.style.SetBackgroundColor(defaultHandleColor);
                sliderInt.OnSliderStopCallback();
            }

            private void MouseDownCallback(MouseDownEvent mouseDownEvent)
            {
                mouseDownEvent.StopImmediatePropagation();

                handle.style.SetBorderColor(sliderInt.elementFactory.highlightColor);
                handle.style.SetBackgroundColor(sliderInt.elementFactory.softWhiteColor);
                dragging = true;
                float ratio = arithmeticProgression.GetClosestTerm(Math.Clamp((mouseDownEvent.localMousePosition.x - radius) / (layout.width - radius), 0f, 1f));
                SetPosition(ratio);
                sliderInt.OnSliderStartCallback();
                sliderInt.OnSliderChangedCallback(ratio);
                this.CaptureMouse();
            }
            private void MouseMoveCallback(MouseMoveEvent mouseMoveEvent)
            {
                if (!dragging) return;
                float ratio = arithmeticProgression.GetClosestTerm(Math.Clamp((mouseMoveEvent.localMousePosition.x - radius) / (layout.width - radius), 0f, 1f));
                SetPosition(ratio);
                sliderInt.OnSliderChangedCallback(ratio);
            }
        }
    }
}